/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-ElasticSearch-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.es.conditions;

import com.taipingframework.boot.es.toolkit.LambdaUtils;
import com.taipingframework.boot.es.toolkit.reflection.property.PropertyName;
import com.taipingframework.boot.es.toolkit.support.ColumnCache;
import com.taipingframework.boot.es.toolkit.support.SFunction;
import com.taipingframework.boot.es.toolkit.support.SerializedLambda;

import java.util.Arrays;
import java.util.Map;

import static java.util.stream.Collectors.joining;

/**
 * Lambda 语法使用 Wrapper
 * <p>统一处理解析 lambda 获取 column</p>
 *
 * @param <T>
 * @param <Children>
 */
public abstract class AbstractLambdaWrapper<T, Children extends AbstractLambdaWrapper<T, Children>> extends AbstractWrapper<T, SFunction<T, ?>, Children> {

    private Map<String, ColumnCache> columnMap = null;
    private boolean initColumnMap = false;

    protected String columnsToString(boolean onlyColumn, SFunction<T, ?>[] columns) {
        return Arrays.stream(columns)
                .map(i -> columnToString(i, onlyColumn))
                .collect(joining(","));
    }

    protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
        return getColumn(LambdaUtils.resolve(column), onlyColumn);
    }

    /**
     * 获取 SerializedLambda 对应的列信息，从 lambda 表达式中推测实体类
     * <p>
     * 如果获取不到列信息，那么本次条件组装将会失败
     *
     * @param lambda     lambda 表达式
     * @param onlyColumn 如果是，结果: "name", 如果否： "name" as "name"
     * @return 列
     * @see SerializedLambda#getImplClass()
     * @see SerializedLambda#getImplMethodName()
     */
    private String getColumn(SerializedLambda lambda, boolean onlyColumn) {
        Class<?> aClass = lambda.getInstantiatedType();
        tryInitCache(aClass);
        String fieldName = PropertyName.methodToProperty(lambda.getImplMethodName());
        ColumnCache columnCache = getColumnCache(fieldName, aClass);
        return onlyColumn ? columnCache.getColumn() : columnCache.getColumnSelect();
    }

    /**
     * 根据lambda类型初始化 columnMap
     *
     * @param lambdaClass lambda表达式的引用类型
     */
    private void tryInitCache(Class<?> lambdaClass) {
        if (!initColumnMap) {
            final Class<T> entityClass = getEntityClass();
            if (entityClass != null) {
                lambdaClass = entityClass;
            } else {
                super.entityClass = (Class<T>) lambdaClass;
            }
            columnMap = LambdaUtils.getColumnMap(lambdaClass);
            initColumnMap = true;
        }

        if (columnMap == null) {
            throw new RuntimeException(String.format("can not find lambda cache for this entity [%s]", lambdaClass.getName()));
        }
    }

    /**
     * 从 columnMap 获取 ColumnCache
     */
    private ColumnCache getColumnCache(String fieldName, Class<?> lambdaClass) {
        ColumnCache columnCache = columnMap.get(LambdaUtils.formatKey(fieldName));
        if (columnCache == null) {
            throw new RuntimeException(String.format("can not find lambda cache for this property [%s] of entity [%s]",
                    fieldName, lambdaClass.getName()));
        }
        return columnCache;
    }

    @Override
    protected String columnToString(SFunction<T, ?> column) {
        return columnToString(column, true);
    }

    @Override
    protected String columnToField(SFunction<T, ?> column) {
        return columnToString(column, false);
    }

}
